<?php
/**
 * Firal CMS
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://firal.org/license
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to firal-dev@googlegroups.com so we can send you a copy immediately.
 *
 * @category   Firal
 * @package    Firal_Cache
 * @subpackage Backend
 * @copyright  Copyright (c) 2008 Firal Developers
 * @license    http://firal.org/license     New BSD License
 * @version    $Id: Firal.php 9 2008-06-15 11:04:56Z pieterkokx $
 */

/**
 * @see Zend_Cache
 */
require_once 'Zend/Cache.php';
/**
 * @see Zend_Cache_Backend_Interface
 */
require_once 'Zend/Cache/Backend/Interface.php';
/**
 * @see Zend_Loader_PluginLoader
 */
require_once 'Zend/Loader/PluginLoader.php';

/**
 * Cache enhancements for firal
 * 
 * @category   Firal
 * @package    Firal_Cache
 * @subpackage Backend
 * @copyright  Copyright (c) 2008 MaakSite.nl Productions. (http://productions.maaksite.nl/)
 * @license    http://www.opensource.org/licenses/bsd-license.php     New BSD License
 */
class Firal_Cache_Backend_Firal implements Zend_Cache_Backend_Interface
{
    
    /**
     * The doctrine connection string
     * 
     * @var string
     */
    protected $_connection;
    
    /**
     * The current backend
     * 
     * @var Zend_Cache_Backend_Interface
     */
    protected $_backend;
    
    /**
     * Emulate tags or not
     * 
     * @var bool
     */
    protected $_emulateTags = false;
    
    
    /**
     * Constructor
     * 
     * @param string $connection
     * @param string $backend
     * @param array $backendOptions
     * 
     * @return void
     */
    public function __construct($backend, array $backendOptions = array(), $connection = '')
    {
        $loader = new Zend_Loader_PluginLoader(array(
            'Zend_Cache_Backend' => 'Zend/Cache/Backend/'
        ));
        
        $class = $loader->load($backend);
        
        $this->_backend = new $class($backendOptions);
        
        $this->_connection = $connection;
        
        $backend = strtolower($backend);
        
        if (($backend != 'file') && ($backend != 'sqlite')) {
            $this->_emulateTags = true;
        }
    }
    
    /**
     * Set the frontend directives
     *
     * @param array $directives assoc of directives
     */
    public function setDirectives($directives)
    {
        $this->_backend->setDirectives($directives);
    }

    /**
     * Test if a cache is available for the given id and (if yes) return it (false else)
     *
     * Note : return value is always "string" (unserialization is done by the core not by the backend)
     *
     * @param  string  $id                     Cache id
     * @param  boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
     * @return string|false cached datas
     */
    public function load($id, $doNotTestCacheValidity = false)
    {
        return $this->_backend->load($id, $doNotTestCacheValidity);
    }

    /**
     * Test if a cache is available or not (for the given id)
     *
     * @param  string $id cache id
     * @return mixed|false (a cache is not available) or "last modified" timestamp (int) of the available cache record
     */
    public function test($id)
    {
        return $this->_backend->test($id);
    }

    /**
     * Save some string datas into a cache record
     *
     * Note : $data is always "string" (serialization is done by the
     * core not by the backend)
     *
     * @param  string $data            Datas to cache
     * @param  string $id              Cache id
     * @param  array $tags             Array of strings, the cache record will be tagged by each string entry
     * @param  int   $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
     * @return boolean true if no problem
     */
    public function save($data, $id, $tags = array(), $specificLifetime = false)
    {
        if ($this->_emulateTags) {
            $allTags = $this->_getTags();
            
            $allTags[$id] = $tags;
            
            $this->_setTags($allTags);
            
            $tags = array();
        }
        
        return $this->_backend->save($data, $id, $tags, $specificLifetime);
    }

    /**
     * Remove a cache record
     *
     * @param  string $id Cache id
     * @return boolean True if no problem
     */
    public function remove($id)
    {
        return $this->_backend->remove($id);
    }

    /**
     * Clean some cache records
     *
     * Available modes are :
     * Zend_Cache::CLEANING_MODE_ALL (default)    => remove all cache entries ($tags is not used)
     * Zend_Cache::CLEANING_MODE_OLD              => remove too old cache entries ($tags is not used)
     * Zend_Cache::CLEANING_MODE_MATCHING_TAG     => remove cache entries matching all given tags
     *                                               ($tags can be an array of strings or a single string)
     * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
     *                                               ($tags can be an array of strings or a single string)
     *
     * @param  string $mode Clean mode
     * @param  array  $tags Array of tags
     * @return boolean true if no problem
     */
    public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
    {
        if ($this->_emulateTags && ($mode == Zend_Cache::CLEANING_MODE_MATCHING_TAG)) {
            $allTags = $this->_getTags();
            
            foreach ($allTags as $id => $delete) {
                foreach ($tags as $tag) {
                    if (in_array($tag, $delete)) {
                        $this->_backend->remove($id);
                        unset($allTags[$id]);
                    }
                }
            }
            
            $this->_setTags($allTags);
            
            $tags = array();
        } elseif ($this->_emulateTags && ($mode == Zend_Cache::CLEANING_MODE_MATCHING_TAG)) {
            $allTags = $this->_getTags();
            
            foreach ($allTags as $id => $delete) {
                $remove = true;
                foreach ($tags as $tag) {
                    if (in_array($tag, $delete)) {
                        $remove = false;
                        break;
                    }
                }
                if ($remove) {
                    $this->_backend->remove($id);
                    unset($allTags[$id]);
                }
            }
            
            $this->_setTags($allTags);
            
            $tags = array();
        } else {
            return $this->_backend->clean($mode, $tags);
        }
    }
    
    /**
     * Can we clear the old cache files automatically?
     * 
     * @return bool
     */
    public function isAutomaticCleaningAvailable()
    {
        return $this->_backend->isAutomaticCleaningAvailable();
    }
    
    /**
     * Get the tags array
     * 
     * The tags array has
     * 
     * @return array
     */
    protected function _getTags()
    {
        if (!($tags = $this->_backend->load('__Firal_Cache_Tags'))) {
            $array = Doctrine_Manager::getInstance()->getConnection($this->_connection)
                ->execute("SELECT * FROM firal_cache_info WHERE name='cache_tags'")->fetch(Doctrine::FETCH_ASSOC);
            
            $tags = $array['value'];
            
            $this->_backend->save($tags, '__Firal_Cache_Tags');
        }
        
        return unserialize($tags);
    }
    
    /**
     * Set the tags array
     * 
     * @param array $tags
     * 
     * @return void
     */
    protected function _setTags(array $tags)
    {
        $serializedTags = serialize($tags);
        
        Doctrine_Manager::getInstance()->getConnection($this->_connection)
            ->execute("UPDATE firal_cache_info SET value=? WHERE name='cache_tags'", array($serializedTags));
        
        $this->_backend->save($serializedTags, '__Firal_Cache_Tags');
    }
}